// Copyright (C) 2024 Kumo inc.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#pragma once

#include <turbo/base/macros.h>              // TURBO_STRINGIFY
#include <kthread/internal/types.h>

__BEGIN_DECLS

// ----------------------------------------------------------------------
// Functions to create 64-bit identifiers that can be attached with data
// and locked without ABA issues. All functions can be called from
// multiple threads simultaneously. Notice that kthread_id_t is designed
// for managing a series of non-heavily-contended actions on an object.
// It's slower than mutex and not proper for general synchronizations.
// ----------------------------------------------------------------------

// Create a kthread_id_t and put it into *id. Crash when `id' is nullptr.
// id->value will never be zero.
// `on_error' will be called after kthread_id_error() is called.
// -------------------------------------------------------------------------
// ! User must call kthread_id_unlock() or kthread_id_unlock_and_destroy()
// ! inside on_error.
// -------------------------------------------------------------------------
// Returns 0 on success, error code otherwise.
int kthread_id_create(
        kthread_id_t *id, void *data,
        int (*on_error)(kthread_id_t id, void *data, int error_code));

// When this function is called successfully, *id, *id+1 ... *id + range - 1
// are mapped to same internal entity. Operations on any of the id work as
// if they're manipulating a same id. `on_error' is called with the id issued
// by corresponding kthread_id_error(). This is designed to let users encode
// versions into identifiers.
// `range' is limited inside [1, 1024].
int kthread_id_create_ranged(
        kthread_id_t *id, void *data,
        int (*on_error)(kthread_id_t id, void *data, int error_code),
        int range);

// Wait until `id' being destroyed.
// Waiting on a destroyed kthread_id_t returns immediately.
// Returns 0 on success, error code otherwise.
int kthread_id_join(kthread_id_t id);

// Destroy a created but never-used kthread_id_t.
// Returns 0 on success, EINVAL otherwise.
int kthread_id_cancel(kthread_id_t id);

// Issue an error to `id'.
// If `id' is not locked, lock the id and run `on_error' immediately. Otherwise
// `on_error' will be called with the error just before `id' being unlocked.
// If `id' is destroyed, un-called on_error are dropped.
// Returns 0 on success, error code otherwise.
#define kthread_id_error(id, err)                                        \
    kthread_id_error_verbose(id, err, __FILE__ ":" TURBO_STRINGIFY(__LINE__))

int kthread_id_error_verbose(kthread_id_t id, int error_code,
                             const char *location);

// Make other kthread_id_lock/kthread_id_trylock on the id fail, the id must
// already be locked. If the id is unlocked later rather than being destroyed,
// effect of this function is cancelled. This function avoids useless
// waiting on a kthread_id which will be destroyed soon but still needs to
// be joinable.
// Returns 0 on success, error code otherwise.
int kthread_id_about_to_destroy(kthread_id_t id);

// Try to lock `id' (for using the data exclusively)
// On success return 0 and set `pdata' with the `data' parameter to
// kthread_id_create[_ranged], EBUSY on already locked, error code otherwise.
int kthread_id_trylock(kthread_id_t id, void **pdata);

// Lock `id' (for using the data exclusively). If `id' is locked
// by others, wait until `id' is unlocked or destroyed.
// On success return 0 and set `pdata' with the `data' parameter to
// kthread_id_create[_ranged], error code otherwise.
#define kthread_id_lock(id, pdata)                                      \
    kthread_id_lock_verbose(id, pdata, __FILE__ ":" TURBO_STRINGIFY(__LINE__))
int kthread_id_lock_verbose(kthread_id_t id, void **pdata,
                            const char *location);

// Lock `id' (for using the data exclusively) and reset the range. If `id' is 
// locked by others, wait until `id' is unlocked or destroyed. if `range' is
// smaller than the original range of this id, nothing happens about the range
#define kthread_id_lock_and_reset_range(id, pdata, range)               \
    kthread_id_lock_and_reset_range_verbose(id, pdata, range,           \
                               __FILE__ ":" TURBO_STRINGIFY(__LINE__))
int kthread_id_lock_and_reset_range_verbose(
        kthread_id_t id, void **pdata,
        int range, const char *location);

// Unlock `id'. Must be called after a successful call to kthread_id_trylock()
// or kthread_id_lock().
// Returns 0 on success, error code otherwise.
int kthread_id_unlock(kthread_id_t id);

// Unlock and destroy `id'. Waiters blocking on kthread_id_join() or
// kthread_id_lock() will wake up. Must be called after a successful call to
// kthread_id_trylock() or kthread_id_lock().
// Returns 0 on success, error code otherwise.
int kthread_id_unlock_and_destroy(kthread_id_t id);

// **************************************************************************
// kthread_id_list_xxx functions are NOT thread-safe unless explicitly stated

// Initialize a list for storing kthread_id_t. When an id is destroyed, it will
// be removed from the list automatically.
// The commented parameters are not used anymore and just kept to not break
// compatibility.
int kthread_id_list_init(kthread_id_list_t *list,
                         unsigned /*size*/,
                         unsigned /*conflict_size*/);
// Destroy the list.
void kthread_id_list_destroy(kthread_id_list_t *list);

// Add a kthread_id_t into the list.
int kthread_id_list_add(kthread_id_list_t *list, kthread_id_t id);

// Swap internal fields of two lists.
void kthread_id_list_swap(kthread_id_list_t *dest,
                          kthread_id_list_t *src);

// Issue error_code to all kthread_id_t inside `list' and clear `list'.
// Notice that this function iterates all id inside the list and may call
// on_error() of each valid id in-place, in another word, this thread-unsafe
// function is not suitable to be enclosed within a lock directly.
// To make the critical section small, swap the list inside the lock and
// reset the swapped list outside the lock as follows:
//   kthread_id_list_t tmplist;
//   kthread_id_list_init(&tmplist, 0, 0);
//   LOCK;
//   kthread_id_list_swap(&tmplist, &the_list_to_reset);
//   UNLOCK;
//   kthread_id_list_reset(&tmplist, error_code);
//   kthread_id_list_destroy(&tmplist);
int kthread_id_list_reset(kthread_id_list_t *list, int error_code);
// Following 2 functions wrap above process.
int kthread_id_list_reset_pthreadsafe(
        kthread_id_list_t *list, int error_code, pthread_mutex_t *mutex);
int kthread_id_list_reset_kthreadsafe(
        kthread_id_list_t *list, int error_code, kthread_mutex_t *mutex);
__END_DECLS

#if defined(__cplusplus)

// cpp specific API, with an extra `error_text' so that error information
// is more comprehensive
int kthread_id_create2(
        kthread_id_t *id, void *data,
        int (*on_error)(kthread_id_t id, void *data, int error_code,
                        const std::string &error_text));

int kthread_id_create2_ranged(
        kthread_id_t *id, void *data,
        int (*on_error)(kthread_id_t id, void *data, int error_code,
                        const std::string &error_text),
        int range);

#define kthread_id_error2(id, ec, et)                                   \
    kthread_id_error2_verbose(id, ec, et, __FILE__ ":" TURBO_STRINGIFY(__LINE__))

int kthread_id_error2_verbose(kthread_id_t id, int error_code,
                              const std::string &error_text,
                              const char *location);

int kthread_id_list_reset2(kthread_id_list_t *list, int error_code,
                           const std::string &error_text);

int kthread_id_list_reset2_pthreadsafe(kthread_id_list_t *list, int error_code,
                                       const std::string &error_text,
                                       pthread_mutex_t *mutex);

int kthread_id_list_reset2_kthreadsafe(kthread_id_list_t *list, int error_code,
                                       const std::string &error_text,
                                       kthread_mutex_t *mutex);

#endif
namespace kthread {
    void id_status(kthread_id_t id, std::ostream &os);

    void id_pool_status(std::ostream &os);
}
